package contegor.scire.core.modifications;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import contegor.scire.core.Connectable;
import contegor.scire.core.Container;
import contegor.scire.core.DuplicateElementException;
import contegor.scire.core.Id;
import contegor.scire.core.IdFragment;
import contegor.scire.core.InfiniteSingletonList;
import contegor.scire.core.Link;
import contegor.scire.core.MissingElementException;
import contegor.scire.core.Modifiable;
import contegor.scire.core.ReturnPoint;
import contegor.scire.core.ReturnPointAdapter;
import contegor.scire.core.Task;
import contegor.scire.core.TaskId;
import contegor.scire.core.TrackExternalLinks;
import contegor.scire.core.TransactionException;
import contegor.scire.core.Version;
import contegor.scire.core.WithDependencies;


/**
 * {@link Modification} used to notify a {@link Modifiable} that it must create inside itself a {@link Link}.
 * The source and target of that link should be <code>{@link Connectable}</code>.
 *
 * @version $Revision$
 * @author  Héctor Cirbián Pousa ($Author$)
 */
@Version("$Id$")
public class CreateLink extends Modification<Link> implements WithDependencies<Void, Link> {
  private static final long serialVersionUID = -2684603407377124636L;

  protected Link.Flow          flow; 
  protected IdFragment         sourceStartPoint;
  protected Id[]               locatorExtra;
  protected Id                 target;
  protected List<Task<Void>>   dependencies;

  public CreateLink(Link.Flow flow, Id containerId, Id target, IdFragment sourceStartPoint, Id ... sourceNextPoints) {
    super(containerId);
    this.flow             = flow;
    this.sourceStartPoint = sourceStartPoint;
    this.locatorExtra     = sourceNextPoints;
    this.target           = target;
    if (!(containerId.isParentOf(target) || Id.RAW_ROOT.isAncestorOf(target))) {
      this.dependencies = new ArrayList<Task<Void>>(1);
      this.dependencies.add(new TrackExternalLinks(target.getParentId(), target.getLastIdFragment(), containerId));
    }
    else {
      this.dependencies = Collections.emptyList();
    }
  }

  @Override
  public int getDepth() {
    return 1 + (this.locatorExtra != null ? this.locatorExtra.length : 0);
  }

  public Id getTarget() {
    return this.target;
  }

  public void process(Container container) throws DuplicateElementException, TransactionException, MissingElementException {
    super.isValidForThisContainer(container);
    returnPoint.manage(this.taskId, ((Modifiable)container).createLink(relatedTime, this.taskId.getTransactionId(), flow, target, sourceStartPoint, locatorExtra));
  }

  @Override
  public List<? extends Task<Void>> getDependencies() {
    return this.dependencies;
  }

  @Override
  public List<? extends ReturnPoint<Void>> getDependenciesReturnPoints() {
    if (this.dependencies == null)
      return Collections.emptyList();
    return new InfiniteSingletonList<ReturnPoint<Void>>(new ReturnPointAdapter<Void>() {
      public void manage(TaskId taskId, Exception exception) {
        CreateLink.this.returnPoint.manage(CreateLink.this.taskId, exception);          
      }
    }, this.dependencies.size());
  }

  @Override
  public void setTaskId(TaskId taskId) {
    super.setTaskId(taskId);
    for (Task<Void> dependencie: dependencies)
      dependencie.setTaskId(taskId.next(true));
  }

  @Override
  public String toString() {
    return "CreateLink [sourceStartPoint=" + sourceStartPoint + ", target=" + target + ", locatorExtra=" + Arrays.toString(locatorExtra) + "]";
  }

}